Micron Document
🎖️GitЯра🎖️

Node / meshtastic / Meshtastic-Android / files / feature / widget / src / test / kotlin / org / meshtastic / feature / widget / LocalStatsWidgetContentTest.kt

Displaying Raw • Download

feature/widget/src/test/kotlin/org/meshtastic/feature/widget/LocalStatsWidgetContentTest.kt fix/fdroid-map-rendering (113121c4) Text, 5.97 KB

T8b949e/*
* Copyright (c) 2026 Meshtastic LLC
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
Tff7b72package T7ee787org.meshtastic.feature.widget

Tff7b72import T7ee787android.app.Application
Tff7b72import T7ee787android.content.Context
Tff7b72import T7ee787androidx.compose.ui.unit.DpSize
Tff7b72import T7ee787androidx.compose.ui.unit.dp
Tff7b72import T7ee787androidx.glance.appwidget.testing.unit.runGlanceAppWidgetUnitTest
Tff7b72import T7ee787androidx.glance.testing.unit.hasContentDescriptionEqualTo
Tff7b72import T7ee787androidx.glance.testing.unit.hasText
Tff7b72import T7ee787androidx.test.core.app.ApplicationProvider
Tff7b72import T7ee787org.junit.runner.RunWith
Tff7b72import T7ee787org.meshtastic.core.model.ConnectionState
Tff7b72import T7ee787org.meshtastic.core.resources.Res
Tff7b72import T7ee787org.meshtastic.core.resources.disconnected
Tff7b72import T7ee787org.meshtastic.core.resources.getString
Tff7b72import T7ee787org.meshtastic.core.resources.refresh
Tff7b72import T7ee787org.robolectric.RobolectricTestRunner
Tff7b72import T7ee787org.robolectric.annotation.Config
Tff7b72import T7ee787kotlin.test.Test
Tff7b72import T7ee787kotlin.time.Duration.Companion.seconds

T8b949e/**
* Unit tests for [LocalStatsWidget.WidgetContent] using Glance's own test harness.
*
* The harness does not inflate views, so these assert on the composed Glance tree rather than pixels. Robolectric is
* required because the content resolves Compose Multiplatform string resources, which needs a real [Context].
*
* The load-bearing assertion across all of these is that *any* string renders at all: every one goes through the
* [WidgetResourceEnvironment] bridge, which hand-provides the `androidx.compose.ui` composition locals that Glance
* omits. If that bridge breaks — most likely because Compose Multiplatform starts reading another local — resolution
* throws and these tests fail here instead of shipping a blank widget.
*/
Tf0883e@RunWithTb4b4b4(Te6edf3RobolectricTestRunnerTff7b72::Te6edf3classTb4b4b4)
Tf0883e@ConfigTb4b4b4(Te6edf3application Tff7b72= Te6edf3ApplicationTff7b72::Te6edf3classTb4b4b4, Te6edf3sdk Tff7b72= Tff7b72[T79c0ff3T79c0ff4Tff7b72]Tb4b4b4)
Tff7b72class T56d364LocalStatsWidgetContentTest Tb4b4b4{

Tff7b72private Tff7b72val Te6edf3contextTb4b4b4: Te6edf3Context Tff7b72= Te6edf3ApplicationProviderTb4b4b4.Te6edf3getApplicationContextTb4b4b4(Tb4b4b4)

T8b949e// Resolved here, at construction, rather than inside the test bodies: getString() wraps runBlocking, and
T8b949e// runGlanceAppWidgetUnitTest runs its block inside runTest, where a nested runBlocking risks deadlocking.
Tff7b72private Tff7b72val Te6edf3disconnectedLabelTb4b4b4: Tffa657String Tff7b72= Te6edf3getStringTb4b4b4(Te6edf3ResTb4b4b4.Te6edf3stringTb4b4b4.Te6edf3disconnectedTb4b4b4)
Tff7b72private Tff7b72val Te6edf3refreshLabelTb4b4b4: Tffa657String Tff7b72= Te6edf3getStringTb4b4b4(Te6edf3ResTb4b4b4.Te6edf3stringTb4b4b4.Te6edf3refreshTb4b4b4)

Tf0883e@Test
Tff7b72fun Td2a8ff`connected content renders node identity and footer counts`Tb4b4b4(Tb4b4b4) Tff7b72=
Te6edf3runGlanceAppWidgetUnitTestTb4b4b4(Te6edf3timeout Tff7b72= Te6edf3TEST_TIMEOUTTb4b4b4) Tb4b4b4{
Te6edf3setAppWidgetSizeTb4b4b4(Te6edf3BIG_SQUARETb4b4b4)
Te6edf3setContextTb4b4b4(Te6edf3contextTb4b4b4)

Te6edf3provideComposable Tb4b4b4{ Te6edf3LocalStatsWidgetTb4b4b4(Tb4b4b4)Tb4b4b4.Te6edf3WidgetContentTb4b4b4(Te6edf3createMockWidgetStateTb4b4b4(Tb4b4b4)Tb4b4b4) Tb4b4b4}

T8b949e// Values straight from createMockWidgetState(): short name, battery, and onlineNodes/totalNodes.
Te6edf3onNodeTb4b4b4(Te6edf3hasTextTb4b4b4(Ta5d6ff"Ta5d6ffMETa5d6ff"Tb4b4b4)Tb4b4b4)Tb4b4b4.Te6edf3assertExistsTb4b4b4(Tb4b4b4)
Te6edf3onNodeTb4b4b4(Te6edf3hasTextTb4b4b4(Ta5d6ff"Ta5d6ff85%Ta5d6ff"Tb4b4b4)Tb4b4b4)Tb4b4b4.Te6edf3assertExistsTb4b4b4(Tb4b4b4)
Te6edf3onNodeTb4b4b4(Te6edf3hasTextTb4b4b4(Ta5d6ff"Ta5d6ff2/3Ta5d6ff"Tb4b4b4)Tb4b4b4)Tb4b4b4.Te6edf3assertExistsTb4b4b4(Tb4b4b4)
Tb4b4b4}

Tf0883e@Test
Tff7b72fun Td2a8ff`disconnected content renders the translated disconnected status`Tb4b4b4(Tb4b4b4) Tff7b72=
Te6edf3runGlanceAppWidgetUnitTestTb4b4b4(Te6edf3timeout Tff7b72= Te6edf3TEST_TIMEOUTTb4b4b4) Tb4b4b4{
Te6edf3setAppWidgetSizeTb4b4b4(Te6edf3BIG_SQUARETb4b4b4)
Te6edf3setContextTb4b4b4(Te6edf3contextTb4b4b4)

Te6edf3provideComposable Tb4b4b4{
Te6edf3LocalStatsWidgetTb4b4b4(Tb4b4b4)
Tb4b4b4.Te6edf3WidgetContentTb4b4b4(
Te6edf3LocalStatsWidgetUiStateTb4b4b4(Te6edf3connectionState Tff7b72= Te6edf3ConnectionStateTb4b4b4.Te6edf3DisconnectedTb4b4b4, Te6edf3showContent Tff7b72= Tff7b72falseTb4b4b4)Tb4b4b4,
Tb4b4b4)
Tb4b4b4}

T8b949e// Proves the Crowdin-managed string resolved through the WidgetResourceEnvironment bridge.
Te6edf3onNodeTb4b4b4(Te6edf3hasTextTb4b4b4(Te6edf3disconnectedLabelTb4b4b4)Tb4b4b4)Tb4b4b4.Te6edf3assertExistsTb4b4b4(Tb4b4b4)
Tb4b4b4}

Tf0883e@Test
Tff7b72fun Td2a8ff`refresh affordance is present and labelled for accessibility`Tb4b4b4(Tb4b4b4) Tff7b72=
Te6edf3runGlanceAppWidgetUnitTestTb4b4b4(Te6edf3timeout Tff7b72= Te6edf3TEST_TIMEOUTTb4b4b4) Tb4b4b4{
Te6edf3setAppWidgetSizeTb4b4b4(Te6edf3BIG_SQUARETb4b4b4)
Te6edf3setContextTb4b4b4(Te6edf3contextTb4b4b4)

Te6edf3provideComposable Tb4b4b4{ Te6edf3LocalStatsWidgetTb4b4b4(Tb4b4b4)Tb4b4b4.Te6edf3WidgetContentTb4b4b4(Te6edf3createMockWidgetStateTb4b4b4(Tb4b4b4)Tb4b4b4) Tb4b4b4}

T8b949e// Glance's unit-test filters have no matcher for actionRunCallback, so this pins the button's
T8b949e// presence and its content description rather than the callback class it dispatches to.
Te6edf3onNodeTb4b4b4(Te6edf3hasContentDescriptionEqualToTb4b4b4(Te6edf3refreshLabelTb4b4b4)Tb4b4b4)Tb4b4b4.Te6edf3assertExistsTb4b4b4(Tb4b4b4)
Tb4b4b4}

Tf0883e@Test
Tff7b72fun Td2a8ff`narrow layout still renders content rather than collapsing`Tb4b4b4(Tb4b4b4) Tff7b72=
Te6edf3runGlanceAppWidgetUnitTestTb4b4b4(Te6edf3timeout Tff7b72= Te6edf3TEST_TIMEOUTTb4b4b4) Tb4b4b4{
T8b949e// Smallest entry in LocalStatsWidget's SizeMode.Responsive set, where isNarrow/isShort both kick in.
Te6edf3setAppWidgetSizeTb4b4b4(Te6edf3SMALL_SQUARETb4b4b4)
Te6edf3setContextTb4b4b4(Te6edf3contextTb4b4b4)

Te6edf3provideComposable Tb4b4b4{ Te6edf3LocalStatsWidgetTb4b4b4(Tb4b4b4)Tb4b4b4.Te6edf3WidgetContentTb4b4b4(Te6edf3createMockWidgetStateTb4b4b4(Tb4b4b4)Tb4b4b4) Tb4b4b4}

Te6edf3onNodeTb4b4b4(Te6edf3hasTextTb4b4b4(Ta5d6ff"Ta5d6ffMETa5d6ff"Tb4b4b4)Tb4b4b4)Tb4b4b4.Te6edf3assertExistsTb4b4b4(Tb4b4b4)
Tb4b4b4}

Tff7b72private Tff7b72companion Tff7b72object Tb4b4b4{
Tff7b72val Te6edf3SMALL_SQUARE Tff7b72= Te6edf3DpSizeTb4b4b4(T79c0ff1T79c0ff0T79c0ff0.Te6edf3dpTb4b4b4, T79c0ff1T79c0ff0T79c0ff0.Te6edf3dpTb4b4b4)
Tff7b72val Te6edf3BIG_SQUARE Tff7b72= Te6edf3DpSizeTb4b4b4(T79c0ff2T79c0ff5T79c0ff0.Te6edf3dpTb4b4b4, T79c0ff2T79c0ff5T79c0ff0.Te6edf3dpTb4b4b4)

T8b949e/**
* Well above Glance's 2-second `DEFAULT_TIMEOUT`, which is not a safe budget here. Booting Robolectric and
* loading Compose Multiplatform resources comfortably exceeds two seconds whenever the machine is busy, and
* `configureTestOptions()` sets `maxParallelForks` to every available processor in CI. The default timeout was
* already observed failing one of these tests with `UncompletedCoroutinesError` during a full baseline run
* while passing in isolation — the classic signature of a too-tight budget rather than a hung test.
*/
Tff7b72val Te6edf3TEST_TIMEOUT Tff7b72= T79c0ff3T79c0ff0.Te6edf3seconds
Tb4b4b4}
Tb4b4b4}

Served by rngit 1.5.0 - Generated in 0.06s